﻿//-----------------------------------------------------------------------
// <copyright file="ImageQuery.cs" company="Microsoft">
//     (c) 2009 Microsoft corp.
//     This software is distributed under Microsoft Public License (MSPL)
//     see http://opensource.org/licenses/ms-pl.html
// </copyright>
//-----------------------------------------------------------------------
namespace LiveSearchApiPowerToys.Components
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Json;
    using System.Text;
    using System.Web;
    using System.Web.Script.Serialization;
    using System.Xml;

    using AjaxControlToolkit;
    using LiveSearchApiPowerToys.Components.net.live.search.api;
    

    #region Enum Types
    /// <summary>
    /// Allowed filter value for the image aspect ratio
    /// </summary>
    public enum ImageAspectRatioOption
    {
        /// <summary>
        /// Represents a square image (width and height are roughly equal).
        /// </summary>
        Square,

        /// <summary>
        /// Represents a wide and short image (width &gt; height).
        /// </summary>
        Wide,

        /// <summary>
        /// Represents a tall and narrow image (width &lt; height).
        /// </summary>
        Tall
    }

    /// <summary>
    /// Allowed filter values for the image size
    /// </summary>
    public enum ImageSizesOption
    {
        /// <summary>
        /// Represents a small image
        /// </summary>
        Small,

        /// <summary>
        /// Represents a medim-sized image
        /// </summary>
        Medium,

        /// <summary>
        /// Represents a large image
        /// </summary>
        Large
    }

    /// <summary>
    /// Allowed filter values for the image color
    /// </summary>
    public enum ImageColorsOption
    {
        /// <summary>
        /// Represents a color image
        /// </summary>
        Color,

        /// <summary>
        /// Represents a black-and-white image
        /// </summary>
        BlackAndWhite
    }

    /// <summary>
    /// Allowed filter values for the image type
    /// </summary>
    public enum ImageTypeOption
    {
        /// <summary>
        /// Represents a photographic image
        /// </summary>
        Photo,

        /// <summary>
        /// Represents a drawing or illustration
        /// </summary>
        Graphics
    }

        /// <summary>
    /// Allowed filter values for the image subject
    /// </summary>
    public enum ImageSubjectOption
    {
        /// <summary>
        /// Represents an image of a face
        /// </summary>
        Face,
        
        /// <summary>
        /// Represents an image of a portrait 
        /// </summary>
        Portrait,
        
        /// <summary>
        /// Represents an image that is not a portrait (for example a landscape)
        /// </summary>
        NonPortrait
    }

    #endregion  

    /// <summary>
    /// Specialization of a query object for image queries. Allows specifying query facets typical of image such as
    /// color or B/W, aspect ratio, subject type, etc.
    /// </summary>
    [DataContract]
    public class ImageQuery : Query
    {
        /// <summary>
        /// The size of the image as {Small, Medium, Large} in the query
        /// </summary>
        private ImageSizesOption? imageSize;

        /// <summary>
        /// The aspect ratio of the image in the query
        /// </summary>
        private ImageAspectRatioOption? imageAspectRatio;

        /// <summary>
        /// The image color in the query
        /// </summary>
        private ImageColorsOption? imageColor;

        /// <summary>
        /// The type of image in the query
        /// </summary>
        private ImageTypeOption? imageType;

        /// <summary>
        /// The subject type in the image in the query
        /// </summary>
        private ImageSubjectOption? imageSubject;

        #region Constructors
                
        /// <summary>
        /// Initializes a new instance of the ImageQuery class
        /// </summary>
        public ImageQuery()
        {
        }

        /// <summary>
        /// Initializes a new instance of the ImageQuery class 
        /// </summary>
        /// <param name="queryText">the original query terms before any augmentation</param>
        /// <param name="queryAugmentation">any custom augmentation to the query string besides image search facets</param>
        /// <param name="mySize">the desired image size as {Small, Medium, Large}</param>
        /// <param name="myAspectRatio">the desired image aspect ratio</param>
        /// <param name="myColor">the desired image color</param>
        /// <param name="myType">the desired image type (photograph vs. illustration)</param>
        /// <param name="mySubject">the desired image subject</param>
        /// <param name="maxNumberOfImages">the maximum numbers of images</param>
        public ImageQuery(
            string queryText,
            string queryAugmentation,
            ImageSizesOption? mySize,
            ImageAspectRatioOption? myAspectRatio,
            ImageColorsOption? myColor,
            ImageTypeOption? myType,
            ImageSubjectOption? mySubject,
            int maxNumberOfImages)
        {
            this.Text = queryText;
            this.Augmentation = queryAugmentation;
            this.ImageSize = mySize;
            this.ImageAspectRatio = myAspectRatio;
            this.ImageColor = myColor;
            this.ImageType = myType;
            this.ImageSubject = mySubject;
            this.MaxNumberOfImages = maxNumberOfImages;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the image size
        /// </summary>
        [DataMember]
        public ImageSizesOption? ImageSize
        {
          get { return this.imageSize; }
          set { this.imageSize = value; }
        }
        
        /// <summary>
        /// Gets or sets the image aspect ratio
        /// </summary>
        [DataMember]
        public ImageAspectRatioOption? ImageAspectRatio
        {
            get { return this.imageAspectRatio; }
            set { this.imageAspectRatio = value; }
        }

        /// <summary>
        /// Gets or sets the image color
        /// </summary>
        [DataMember]
        public ImageColorsOption? ImageColor
        {
            get { return this.imageColor; }
            set { this.imageColor = value; }
        }

        /// <summary>
        /// Gets or sets the image type
        /// </summary>
        [DataMember]
        public ImageTypeOption? ImageType
        {
            get { return this.imageType; }
            set { this.imageType = value; }
        }

        /// <summary>
        /// Gets or sets the image subject
        /// </summary>
        [DataMember]
        public ImageSubjectOption? ImageSubject
        {
            get { return this.imageSubject; }
            set { this.imageSubject = value; }
        }

        /// <summary>
        /// Gets or sets the maximum number of images
        /// </summary>
        [DataMember]
        public int MaxNumberOfImages
        {
            get; set;
        }
        #endregion
      
        #region Serialization

        /// <summary>
        /// Create a new ImageQuery object from a JSON serialized string
        /// </summary>
        /// <param name="serializedImageQuery">JSON string</param>
        /// <returns>an ImageQuery built from the JSON stream</returns>
        public static ImageQuery CreateImageQueryFromJson(string serializedImageQuery)
        {
            ImageQuery imgQueryResult = null;
            using (MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(serializedImageQuery)))
            {
                DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(ImageQuery));
                imgQueryResult = (ImageQuery)jsonSerializer.ReadObject(stream);
            }

            return imgQueryResult;
        }

        /// <summary>
        /// Serialize the ImageQuery object as JSON 
        /// </summary>
        /// <returns>A JSON string or empty string if any errors</returns>
        public override string ToJsonRequest()
        {          
            string serializedRequest = string.Empty;
            List<Type> listOfTypes = new List<Type>();
            listOfTypes.Add(typeof(Uri));
            listOfTypes.Add(typeof(List<Uri>));
            listOfTypes.Add(typeof(ImageSizesOption));
            listOfTypes.Add(typeof(ImageColorsOption));
            listOfTypes.Add(typeof(ImageAspectRatioOption));
            listOfTypes.Add(typeof(ImageTypeOption));
            listOfTypes.Add(typeof(ImageSubjectOption));
            DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(ImageQuery), listOfTypes);
            using (MemoryStream stream = new MemoryStream())
            {
                jsonSerializer.WriteObject(stream, this);
                serializedRequest = Encoding.GetEncoding("iso-8859-1").GetString(stream.ToArray());
            }

            return serializedRequest;
        }

        /// <summary>
        /// Builds an Image Search Query string to be used to invoke LiveSearch API ImageSearch request
        /// </summary>
        /// <param name="myAppId">The LiveSearch API Application ID</param>
        /// <param name="imageOffset">Ask for images starting after imageOffset images in the results set</param>
        /// <param name="myAdultOption">Adult Option</param>
        /// <returns>A SearchRequest object to be used to invoke LiveSearch API</returns>
        public SearchRequest BuildRequest(string myAppId, uint imageOffset, AdultOption myAdultOption)
        {
            SearchRequest request = new SearchRequest();

            request.AppId = myAppId;
            request.Sources = new SourceType[] { SourceType.Image };

            // Image-specific request fields (optional)
            request.Image = new ImageRequest();

            request.Image.Count = 50; // Always ask for maximum
            request.Image.CountSpecified = true;
            request.Image.Offset = imageOffset; // Get images starting from a certain offset;
            request.Image.OffsetSpecified = true;

            // Common request fields (required)        
            request.Query = this.LiveSearchExpression();

            // Common request fields (optional)
            request.Version = "2.0";
            request.Market = "en-us";
            request.Adult = myAdultOption;
            request.AdultSpecified = true;

            return request;
        }

        #endregion  

        /// <summary>
        /// Execute this ImageQuery on LiveSearch API to retrieve images
        /// </summary>
        /// <param name="myAppId">The LiveSearch API Application ID</param>
        /// <returns>An array of ImageResult</returns>
        public ImageResult[] ExecuteQuery(String myAppId)
        {
            ImageResult[] slides = null;

            // Fetch all possible images
            uint offset = 0;
            int numberOfImagesFound = 0;
            using (LiveSearchService service = new LiveSearchService())
            {
                try
                {
                    //Create a new empty list of images
                    List<ImageResult> myImages = new List<ImageResult>();
                    do
                    {
                        try
                        {
                            //Build the request for the right query at the right offset
                            SearchRequest request = this.BuildRequest(myAppId, offset, AdultOption.Moderate);

                            // Send the request; build the response.
                            SearchResponse response = service.Search(request);

                            // Check if a number of images has been found - if so append these images to the result list
                            numberOfImagesFound = this.ProcessSearchImagesResults(response, myImages);

                            //Get to the next bunch of images
                            offset += 50;
                        }
                        catch (System.Web.Services.Protocols.SoapException ex)
                        {
                            // A SOAP Exception was thrown, log error details.
                            this.ProcessSearchImagesErrors(ex.Detail);
                        }
                        catch (System.Net.WebException ex)
                        {
                            // An exception occurred while accessing the network.
                            Trace.TraceError("ImagesHelper Service - SearchPhotos", "Query = " + this.ToJsonRequest() + " WebException: " + ex.Message);
                        }

                    }
                    while ((numberOfImagesFound > 0) && (myImages.Count < this.MaxNumberOfImages));

                    // If any image has been found return them into an array
                    int nOfElems = (this.MaxNumberOfImages > myImages.Count) ? myImages.Count : this.MaxNumberOfImages;
                    slides = new ImageResult[nOfElems];
                    myImages.CopyTo(0, slides, 0, nOfElems);
                }
                catch (WebException ex)
                {
                    // An exception occurred while accessing the network.
                    Trace.TraceError("ImagesHelper Service - SearchPhotos", "Image Query = " + this.ToJsonRequest() + " WebException: " + ex.Message);
                }
            }
            return slides;
        }

        /// <summary>
        /// Based on the values of object properties, returns a well formed LiveSearch query expression for the image index
        /// </summary>
        /// <returns>A Live Search query string </returns>
        protected override string LiveSearchExpression()
        {
            string optionType;
            switch (this.ImageType)
            {
                case ImageTypeOption.Photo:
                    optionType = " filterui:photo-photo ";
                    break;
                case ImageTypeOption.Graphics:
                    optionType = " filterui:photo-graphics ";
                    break;

                default: optionType = string.Empty;
                    break;
            }

            string optionSize;
            switch (this.ImageSize)
            {
                case ImageSizesOption.Large:
                    optionSize = " filterui:imagesize-large ";
                    break;
                case ImageSizesOption.Medium:
                    optionSize = " filterui:imagesize-medium ";
                    break;
                case ImageSizesOption.Small:
                    optionSize = " filterui:imagesize-small ";
                    break;

                default: optionSize = string.Empty;
                    break;
            }

            // Alter query to get images of desired color
            string optionColor;
            switch (this.ImageColor)
            {
                case ImageColorsOption.Color:
                    optionColor = " filterui:color-color ";
                    break;
                case ImageColorsOption.BlackAndWhite:
                    optionColor = " filterui:color-bw ";
                    break;
                default: optionColor = string.Empty;
                    break;
            }

            // Alter query to get images of desired aspect ratio
            string optionAspectRatio;
            switch (this.ImageAspectRatio)
            {
                case ImageAspectRatioOption.Square:
                    optionAspectRatio = " filterui:aspect-square ";
                    break;
                case ImageAspectRatioOption.Tall:
                    optionAspectRatio = " filterui:aspect-tall ";
                    break;
                case ImageAspectRatioOption.Wide:
                    optionAspectRatio = " filterui:aspect-wide ";
                    break;
                default: optionAspectRatio = string.Empty;
                    break;
            }

            // Alter query to get images of desired aspect ratio
            string optionSubject;
            switch (this.ImageSubject)
            {
                case ImageSubjectOption.Face:
                    optionSubject = " filterui:face-face ";
                    break;
                case ImageSubjectOption.Portrait:
                    optionSubject = " filterui:face-portrait ";
                    break;
                case ImageSubjectOption.NonPortrait:
                    optionSubject = " filterui:face-nonportrait ";
                    break;
                default: optionSubject = string.Empty;
                    break;
            }

            return base.LiveSearchExpression() + optionAspectRatio + optionColor + optionSize + optionSubject + optionType;
        }
        
        
        /// <summary>
        /// Appends the ImageResults from the SearchResponse into a list of ImageResults
        /// </summary>
        /// <param name="response">Array of Search Responses</param>
        /// <param name="myImages">List of ImagesResults</param>
        /// <returns>Number of images appended to the list</returns>
        private int ProcessSearchImagesResults(SearchResponse response, List<ImageResult> myImages)
        {
            int nFound = 0;
            if (response.Image.Results != null)
            {
                foreach (ImageResult result in response.Image.Results)
                {
                    myImages.Add(result);

                    nFound++;
                }
            }
            return nFound;
        }
        /// <summary>
        /// Process LiveSearch Errors
        /// </summary>
        /// <param name="errorDetails"></param>
        private void ProcessSearchImagesErrors(XmlNode errorDetails)
        {
            // Add the default namespace to the namespace manager.
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(
                errorDetails.OwnerDocument.NameTable);
            nsmgr.AddNamespace("api", "http://schemas.microsoft.com/LiveSearch/2008/03/Search");

            XmlNodeList errors = errorDetails.SelectNodes("./api:Errors/api:Error", nsmgr);

            if (errors != null)
            {
                // Iterate over the list of errors and log error details.            
                foreach (XmlNode error in errors)
                {
                    foreach (XmlNode detail in error.ChildNodes)
                    {
                        Trace.TraceError("ImagesHelper Service - Image Search Error", detail.Name + ": " + detail.InnerText);
                    }
                }
            }
        }         
    }
}
